package org.zn.mvcbase.mybatis.plugin;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import org.zn.mvcbase.utils.WhSchemaUtils;

import com.google.common.base.Strings;

/**
* 
* @Company OBOR
* @Author 周楠
* @Description
* 2018年11月6日
*
*/
@Component
@Intercepts(
	    {
	        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
	        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
	        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
	    }
	)
public class SchemaInterceptor implements Interceptor{
	
    private Field additionalParametersField;
	
	public SchemaInterceptor() {
		// TODO Auto-generated constructor stub
		try {
			this.additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
			this.additionalParametersField.setAccessible(true);
		} catch (NoSuchFieldException | SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		// TODO Auto-generated method stub
		
		Executor executor = (Executor) invocation.getTarget();
		Object[] args = invocation.getArgs();
	    MappedStatement ms = (MappedStatement) args[0];
	    Object parameter = args[1];
	    
	    SqlCommandType commondType = ms.getSqlCommandType();
	       
	    RowBounds rowBounds=null;
	    ResultHandler resultHandler=null;
	             
	      
	    CacheKey cacheKey=null;
	    BoundSql boundSql = ms.getBoundSql(parameter);
	        
	    
	        if(args.length > 2){
	        	 rowBounds = (RowBounds) args[2];
		         resultHandler = (ResultHandler) args[3];
	            cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
	        } 
	        if(args.length == 6){
	            //6 个参数时
	            cacheKey = (CacheKey) args[4];
	            boundSql = (BoundSql) args[5];
	        } 
	    
	        
	        if(!Strings.isNullOrEmpty(WhSchemaUtils.getSchema())) {
	        	
	        Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
	        	
	        String dealSql=boundSql.getSql().replaceAll("@schema", WhSchemaUtils.getSchema());	   
	        
	        boundSql=new BoundSql(ms.getConfiguration(), dealSql, boundSql.getParameterMappings(), parameter);
	        
	        for (String key : additionalParameters.keySet()) {
	        	boundSql.setAdditionalParameter(key, additionalParameters.get(key));
            }
	           
	         ms = newMappedStatement(ms, new BoundSqlSqlSource(boundSql));
	        args[0]=ms;
	      
	        }
	        
	        if(commondType==SqlCommandType.SELECT) {
	        	return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
	        }
	        else  {
	        	
	        	return  invocation.proceed();
	        	
	        }
	       
	}

	@Override
	public Object plugin(Object target) {
		// TODO Auto-generated method stub

	     return Plugin.wrap(target, this); 
	}

	@Override
	public void setProperties(Properties properties) {
		// TODO Auto-generated method stub
		
	}

	
	
	 
	    /**
	     * 包装sql后，重置到invocation中
	     * @param invocation
	     * @param sql
	     * @throws SQLException
	     */
		@SuppressWarnings("unused")
		@Deprecated
	    private void resetSql2Invocation(Invocation invocation,MappedStatement statement,Object parameterObject, BoundSql sql) throws SQLException {
	
	
	        BoundSql boundSql = statement.getBoundSql(parameterObject);
	        MappedStatement newStatement = newMappedStatement(statement, new BoundSqlSqlSource(sql));
	        MetaObject msObject =  MetaObject.forObject(newStatement, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(),new DefaultReflectorFactory());
	        msObject.setValue("sqlSource.boundSql.sql", sql);
	        invocation.getArgs()[0] = newStatement;
	    }
	 
	    private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
	        MappedStatement.Builder builder =
	                new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
	        builder.resource(ms.getResource());
	        builder.fetchSize(ms.getFetchSize());
	        builder.statementType(ms.getStatementType());
	        builder.keyGenerator(ms.getKeyGenerator());
	        if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
	            StringBuilder keyProperties = new StringBuilder();
	            for (String keyProperty : ms.getKeyProperties()) {
	                keyProperties.append(keyProperty).append(",");
	            }
	            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
	            builder.keyProperty(keyProperties.toString());
	        }
	        builder.timeout(ms.getTimeout());
	        builder.parameterMap(ms.getParameterMap());
	        builder.resultMaps(ms.getResultMaps());
	        builder.resultSetType(ms.getResultSetType());
	        builder.cache(ms.getCache());
	        builder.flushCacheRequired(ms.isFlushCacheRequired());
	        builder.useCache(ms.isUseCache());
	 
	        return builder.build();
	    }
	 
	    
	    
	    
//	    定义一个内部辅助类，作用是包装sq
	    class BoundSqlSqlSource implements SqlSource {
	        private BoundSql boundSql;
	        public BoundSqlSqlSource(BoundSql boundSql) {
	            this.boundSql = boundSql;
	        }
	        @Override
	        public BoundSql getBoundSql(Object parameterObject) {
	            return boundSql;
	        }
	    }
	

}
